<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <title>三角区域弹球动画</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
      background: #f0f0f0;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
    }

    #container {
      position: relative;
    }

    #triangleCanvas {
      border: 1px solid #333;
      background: white;
    }
  </style>
</head>

<body>
  <div id="container">
    <canvas id="triangleCanvas" width="600" height="500"></canvas>
  </div>

  <script>
    const canvas = document.getElementById('triangleCanvas');
    const ctx = canvas.getContext('2d');

    // 三角形顶点坐标
    const triangle = {
      top: { x: canvas.width / 2, y: 50 },
      left: { x: 50, y: canvas.height - 50 },
      right: { x: canvas.width - 50, y: canvas.height - 50 }
    };

    // 小球属性
    const ball = {
      x: canvas.width / 2,
      y: canvas.height / 2,
      radius: 15,
      speedX: 3,
      speedY: 4,
      color: '#FF5252'
    };

    // [新增] 计算三角形各边的法向量（指向内部）
    // 计算左边（top->left）的法向量
    let dx = triangle.left.x - triangle.top.x;
    let dy = triangle.left.y - triangle.top.y;
    let nx_left = dy;
    let ny_left = -dx;
    let len_left = Math.sqrt(nx_left * nx_left + ny_left * ny_left);
    nx_left /= len_left;
    ny_left /= len_left;

    // 计算右边（top->right）的法向量
    dx = triangle.right.x - triangle.top.x;
    dy = triangle.right.y - triangle.top.y;
    let nx_right = -dy;
    let ny_right = dx;
    let len_right = Math.sqrt(nx_right * nx_right + ny_right * ny_right);
    nx_right /= len_right;
    ny_right /= len_right;

    // 计算底边（left->right）的法向量
    dx = triangle.right.x - triangle.left.x;
    dy = triangle.right.y - triangle.left.y;
    let nx_bottom = dy;
    let ny_bottom = -dx;
    let len_bottom = Math.sqrt(nx_bottom * nx_bottom + ny_bottom * ny_bottom);
    nx_bottom /= len_bottom;
    ny_bottom /= len_bottom;

    // 绘制三角形边界
    function drawTriangle() {
      ctx.beginPath();
      ctx.moveTo(triangle.top.x, triangle.top.y);
      ctx.lineTo(triangle.left.x, triangle.left.y);
      ctx.lineTo(triangle.right.x, triangle.right.y);
      ctx.closePath();
      ctx.strokeStyle = '#333';
      ctx.lineWidth = 2;
      ctx.stroke();
    }

    // 绘制小球
    function drawBall() {
      ctx.beginPath();
      ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
      ctx.fillStyle = ball.color;
      ctx.fill();
      ctx.closePath();
    }

    // [修改] 检测小球与三角形边的碰撞 - 使用法向量计算反弹
    function checkCollision() {
      // 定义三条边的信息：起点、终点、法向量
      const edges = [
        { start: triangle.top, end: triangle.left, normal: { x: nx_left, y: ny_left } },
        { start: triangle.top, end: triangle.right, normal: { x: nx_right, y: ny_right } },
        { start: triangle.left, end: triangle.right, normal: { x: nx_bottom, y: ny_bottom } }
      ];

      for (let i = 0; i < edges.length; i++) {
        const edge = edges[i];
        if (isPointOnLine(ball.x, ball.y, edge.start, edge.end, ball.radius)) {
          // 计算速度向量与法向量的点积
          const dot = ball.speedX * edge.normal.x + ball.speedY * edge.normal.y;
          // 反射公式：V' = V - 2 * (V · N) * N
          ball.speedX = ball.speedX - 2 * dot * edge.normal.x;
          ball.speedY = ball.speedY - 2 * dot * edge.normal.y;
          // 只处理第一个碰撞的边
          break;
        }
      }
    }

    // 判断点是否靠近线段（考虑半径）
    function isPointOnLine(px, py, lineStart, lineEnd, radius) {
      const A = px - lineStart.x;
      const B = py - lineStart.y;
      const C = lineEnd.x - lineStart.x;
      const D = lineEnd.y - lineStart.y;

      const dot = A * C + B * D;
      const lenSq = C * C + D * D;
      let param = -1;

      if (lenSq !== 0) {
        param = dot / lenSq;
      }

      let xx, yy;
      if (param < 0) {
        xx = lineStart.x;
        yy = lineStart.y;
      } else if (param > 1) {
        xx = lineEnd.x;
        yy = lineEnd.y;
      } else {
        xx = lineStart.x + param * C;
        yy = lineStart.y + param * D;
      }

      const dx = px - xx;
      const dy = py - yy;
      return Math.sqrt(dx * dx + dy * dy) < radius;
    }

    // 更新动画帧
    function update() {
      // 清除画布
      ctx.clearRect(0, 0, canvas.width, canvas.height);

      // 绘制三角形
      drawTriangle();

      // 更新小球位置
      ball.x += ball.speedX;
      ball.y += ball.speedY;

      // 检测碰撞并反弹
      checkCollision();

      // 绘制小球
      drawBall();

      // 继续动画
      requestAnimationFrame(update);
    }

    // 启动动画
    update();
  </script>
</body>

</html>